Example 2 Clustering Geometric ObjectsΒΆ

In this example we will look at a few of the tools provided by the clifford package for (4,1) conformal geometric algebra (CGA) and see how we can use them in a practical setting to cluster geometric objects via the simple K-means clustering algorithm provided in clifford.tools

As before the first step in using the package for CGA is to generate and import the algebra:

In [1]:
from clifford.g3c import *
print('e1*e1 ', e1*e1)
print('e2*e2 ', e2*e2)
print('e3*e3 ', e3*e3)
print('e4*e4 ', e4*e4)
print('e5*e5 ', e5*e5)
e1*e1  1.0
e2*e2  1.0
e3*e3  1.0
e4*e4  1.0
e5*e5  -1.0

The tools submodule of the clifford package contains a wide array of algorithms and tools that can be useful for manipulating objects in CGA. In this case we will be generating a large number of objects and then segmenting them into clusters.

We first need an algorithm for generating a cluster of objects in space. We will construct this cluster by generating a random object and then repeatedly disturbing this object by some small fixed amount and storing the result:

In [2]:
from clifford.tools.g3c import *
import numpy as np

def generate_random_object_cluster(n_objects, object_generator, max_cluster_trans=1.0, max_cluster_rot=np.pi/8):
    """ Creates a cluster of random objects """
    ref_obj = object_generator()
    cluster_objects = []
    for i in range(n_objects):
        r = random_rotation_translation_rotor(maximum_translation=max_cluster_trans, maximum_angle=max_cluster_rot)
        new_obj = apply_rotor(ref_obj, r)
        cluster_objects.append(new_obj)
    return cluster_objects

We can use this function to create a cluster and then we can visualise this cluster with GAOnline using the built in tools in clifford.

In [3]:
from clifford.tools.g3c.GAOnline import *
clustered_circles = generate_random_object_cluster(10, random_circle)
sc = GAScene()
for c in clustered_circles:
    sc.add_circle(c,'rgb(255,0,0)')
print(sc)
DrawCircle(-(0.10616^e123) - (0.36013^e124) - (0.35316^e125) + (0.32031^e134) + (0.5098^e135) + (0.66383^e145) + (0.26015^e234) + (0.2013^e235) - (0.18258^e245) + (0.64194^e345),rgb(255,0,0));
DrawCircle(-(0.17598^e123) - (0.66529^e124) - (0.6683^e125) - (0.01592^e134) + (0.18028^e135) + (0.74199^e145) + (0.61396^e234) + (0.58369^e235) - (0.12493^e245) + (0.68176^e345),rgb(255,0,0));
DrawCircle(-(0.14871^e123) - (0.60044^e124) - (0.56651^e125) - (0.1825^e134) + (0.01276^e135) + (0.74676^e145) + (0.57285^e234) + (0.56091^e235) + (0.08251^e245) + (0.73752^e345),rgb(255,0,0));
DrawCircle(-(0.10342^e123) - (0.35447^e124) - (0.33772^e125) + (0.06701^e134) + (0.26417^e135) + (0.68664^e145) + (0.36429^e234) + (0.34798^e235) + (0.00313^e245) + (0.70506^e345),rgb(255,0,0));
DrawCircle(-(0.06212^e123) - (0.20295^e124) - (0.18546^e125) + (0.20929^e134) + (0.40679^e135) + (0.70414^e145) + (0.18703^e234) + (0.1766^e235) + (0.01857^e245) + (0.62977^e345),rgb(255,0,0));
DrawCircle((0.00365^e123) - (0.02019^e124) + (0.02067^e125) - (0.15528^e134) + (0.03777^e135) + (0.67086^e145) - (0.0313^e234) - (0.00934^e235) + (0.22907^e245) + (0.72183^e345),rgb(255,0,0));
DrawCircle(-(0.08165^e123) - (0.33479^e124) - (0.32105^e125) + (0.16504^e134) + (0.35089^e135) + (0.78982^e145) + (0.1581^e234) + (0.08961^e235) - (0.25423^e245) + (0.49832^e345),rgb(255,0,0));
DrawCircle(-(0.10476^e123) - (0.39544^e124) - (0.39036^e125) + (0.10485^e134) + (0.29823^e135) + (0.73504^e145) + (0.31825^e234) + (0.27354^e235) - (0.15332^e245) + (0.63222^e345),rgb(255,0,0));
DrawCircle(-(0.04528^e123) - (0.14991^e124) - (0.13107^e125) - (0.17288^e134) + (0.02475^e135) + (0.58243^e145) + (0.193^e234) + (0.19042^e235) + (0.07175^e245) + (0.83261^e345),rgb(255,0,0));
DrawCircle(-(0.08151^e123) - (0.30843^e124) - (0.30609^e125) + (0.19624^e134) + (0.38628^e135) + (0.72471^e145) + (0.19305^e234) + (0.13561^e235) - (0.21182^e245) + (0.58837^e345),rgb(255,0,0));

This cluster generation function appears in clifford tools by default and it can be imported as follows:

In [4]:
from clifford.tools.g3c import generate_random_object_cluster

Now that we can generate individual clusters we would like to generate many:

In [5]:
def generate_n_clusters( object_generator, n_clusters, n_objects_per_cluster ):
    object_clusters = []
    for i in range(n_clusters):
        cluster_objects = generate_random_object_cluster(n_objects_per_cluster, object_generator,
                                                         max_cluster_trans=0.5, max_cluster_rot=np.pi / 16)
        object_clusters.append(cluster_objects)
    all_objects = [item for sublist in object_clusters for item in sublist]
    return all_objects, object_clusters

Again this function appears by default in clifford tools and we can easily visualise the result:

In [6]:
from clifford.tools.g3c import generate_n_clusters

all_objects, object_clusters = generate_n_clusters(random_circle, 2, 5)
sc = GAScene()
for c in all_objects:
    sc.add_circle(c,'rgb(255,0,0)')
print(sc)
DrawCircle(-(0.26974^e123) + (2.20083^e124) + (2.29872^e125) + (0.65898^e134) + (0.68594^e135) + (0.0192^e145) + (2.64164^e234) + (2.75133^e235) + (0.06381^e245) - (0.00394^e345),rgb(255,0,0));
DrawCircle(-(0.2928^e123) + (2.44593^e124) + (2.55366^e125) + (0.42278^e134) + (0.44325^e135) - (0.01546^e145) + (2.51937^e234) + (2.62095^e235) + (0.07846^e245) + (0.02949^e345),rgb(255,0,0));
DrawCircle(-(0.31127^e123) + (2.401^e124) + (2.50778^e125) + (0.49656^e134) + (0.51798^e135) + (0.00509^e145) + (2.59066^e234) + (2.69304^e235) + (0.09901^e245) + (0.01499^e345),rgb(255,0,0));
DrawCircle(-(0.31749^e123) + (2.41156^e124) + (2.52112^e125) + (0.41924^e134) + (0.43754^e135) + (0.00569^e145) + (2.58426^e234) + (2.68429^e235) + (0.13195^e245) + (0.01684^e345),rgb(255,0,0));
DrawCircle(-(0.27021^e123) + (2.23794^e124) + (2.33671^e125) + (0.51143^e134) + (0.53468^e135) - (0.00562^e145) + (2.6466^e234) + (2.75636^e235) + (0.05843^e245) + (0.02^e345),rgb(255,0,0));
DrawCircle((0.22217^e123) + (1.06894^e124) + (1.31025^e125) - (0.25587^e134) - (0.30016^e135) + (0.0648^e145) + (0.04428^e234) - (0.08022^e235) - (0.64712^e245) + (0.15222^e345),rgb(255,0,0));
DrawCircle((0.23175^e123) + (1.10572^e124) + (1.31634^e125) - (0.56568^e134) - (0.66383^e135) + (0.04583^e145) - (0.20784^e234) - (0.35515^e235) - (0.51394^e245) + (0.27154^e345),rgb(255,0,0));
DrawCircle((0.23107^e123) + (1.14079^e124) + (1.38491^e125) - (0.21748^e134) - (0.25945^e135) + (0.02255^e145) + (0.03642^e234) - (0.08332^e235) - (0.62964^e245) + (0.11932^e345),rgb(255,0,0));
DrawCircle((0.24494^e123) + (1.19157^e124) + (1.4088^e125) - (0.29815^e134) - (0.35749^e135) - (0.02423^e145) - (0.20401^e234) - (0.36206^e235) - (0.58796^e245) + (0.14297^e345),rgb(255,0,0));
DrawCircle((0.12258^e123) + (0.97973^e124) + (1.18461^e125) - (0.36684^e134) - (0.44534^e135) - (0.01423^e145) - (0.47417^e234) - (0.64017^e235) - (0.53419^e245) + (0.19313^e345),rgb(255,0,0));

Given that we can now generate multiple clusters of objects we can test algorithms for segmenting them.

The function run_n_clusters below generates a lot of objects distributed into n clusters and then attempts to segment the objects to recover the clusters.

In [7]:
from clifford.tools.g3c.object_clustering import n_clusters_objects
import time

def run_n_clusters( object_generator, n_clusters, n_objects_per_cluster, n_shotgunning):
    all_objects, object_clusters = generate_n_clusters( object_generator, n_clusters, n_objects_per_cluster )
    [new_labels, centroids, start_labels, start_centroids] = n_clusters_objects(n_clusters, all_objects,
                                                                                initial_centroids=None,
                                                                                n_shotgunning=n_shotgunning,
                                                                                averaging_method='unweighted')
    return all_objects, new_labels, centroids

Lets try it!

In [8]:
from clifford.tools.g3c.object_clustering import visualise_n_clusters

object_generator = random_circle

n_clusters = 3
n_objects_per_cluster = 10
n_shotgunning = 60
all_objects, labels, centroids = run_n_clusters(object_generator, n_clusters,
                                                     n_objects_per_cluster, n_shotgunning)

sc = visualise_n_clusters(all_objects, centroids, labels, object_type='circle',
                     color_1=np.array([255, 0, 0]), color_2=np.array([0, 255, 0]))
print(sc)
DrawCircle(-(0.09724^e123) + (0.29203^e124) + (0.44736^e125) + (0.05165^e134) - (0.00902^e135) + (0.26471^e145) + (0.48311^e234) + (0.44543^e235) + (0.88482^e245) - (0.28142^e345),rgb(127, 127, 0));
DrawCircle(-(0.03116^e123) - (0.05613^e124) + (0.09205^e125) + (0.08431^e134) + (0.01993^e135) + (0.285^e145) + (0.21565^e234) + (0.15974^e235) + (0.92493^e245) - (0.29431^e345),rgb(127, 127, 0));
DrawCircle(-(0.03738^e123) - (0.10204^e124) + (0.05417^e125) + (0.07846^e134) + (0.02656^e135) + (0.18621^e145) + (0.26108^e234) + (0.2149^e235) + (0.96494^e245) - (0.26557^e345),rgb(127, 127, 0));
DrawCircle(-(0.08458^e123) + (0.15221^e124) + (0.30776^e125) + (0.08604^e134) + (0.0329^e135) + (0.25385^e145) + (0.45847^e234) + (0.41148^e235) + (0.92771^e245) - (0.24024^e345),rgb(127, 127, 0));
DrawCircle(-(0.06959^e123) + (0.11964^e124) + (0.27642^e125) + (0.06784^e134) + (0.02292^e135) + (0.23009^e145) + (0.38009^e234) + (0.32881^e235) + (0.94449^e245) - (0.1954^e345),rgb(127, 127, 0));
DrawCircle(-(0.06488^e123) + (0.15325^e124) + (0.31257^e125) - (0.00329^e134) - (0.05327^e135) + (0.10998^e145) + (0.34402^e234) + (0.30733^e235) + (0.93152^e245) - (0.2669^e345),rgb(127, 127, 0));
DrawCircle(-(0.12675^e123) + (0.38127^e124) + (0.52651^e125) + (0.11524^e134) + (0.04091^e135) + (0.35565^e145) + (0.61233^e234) + (0.56126^e235) + (0.85528^e245) - (0.31267^e345),rgb(127, 127, 0));
DrawCircle(-(0.05254^e123) - (0.0222^e124) + (0.13617^e125) + (0.08085^e134) + (0.03692^e135) + (0.22516^e145) + (0.32609^e234) + (0.27901^e235) + (0.96311^e245) - (0.20024^e345),rgb(127, 127, 0));
DrawCircle(-(0.11555^e123) + (0.16276^e124) + (0.3117^e125) + (0.12178^e134) + (0.0558^e135) + (0.24992^e145) + (0.66018^e234) + (0.60832^e235) + (0.92403^e245) - (0.32234^e345),rgb(127, 127, 0));
DrawCircle(-(0.06974^e123) + (0.15175^e124) + (0.29692^e125) - (0.00751^e134) - (0.08271^e135) + (0.14799^e145) + (0.37214^e234) + (0.32214^e235) + (0.8834^e245) - (0.40664^e345),rgb(127, 127, 0));
DrawCircle((0.01307^e123) + (0.06946^e124) + (0.0551^e125) - (0.01343^e134) - (0.18628^e135) - (0.93313^e145) + (0.01981^e234) - (0.00633^e235) - (0.11709^e245) + (0.28872^e345),rgb(0, 255, 0));
DrawCircle((0.09996^e123) + (0.52735^e124) + (0.52347^e125) - (0.10475^e134) - (0.27977^e135) - (0.92738^e145) + (0.16237^e234) + (0.15138^e235) - (0.0517^e245) + (0.29581^e345),rgb(0, 255, 0));
DrawCircle(-(0.01414^e123) - (0.07818^e124) - (0.08006^e125) - (0.10633^e134) - (0.28172^e135) - (0.95524^e145) - (0.01096^e234) - (0.01053^e235) + (0.00384^e245) + (0.13912^e345),rgb(0, 255, 0));
DrawCircle(-(0.00924^e123) - (0.06659^e124) - (0.092^e125) - (0.10923^e134) - (0.28204^e135) - (0.94531^e145) - (0.01937^e234) - (0.03534^e235) - (0.06185^e245) + (0.17356^e345),rgb(0, 255, 0));
DrawCircle((0.04226^e123) + (0.22969^e124) + (0.20996^e125) - (0.02812^e134) - (0.20232^e135) - (0.95996^e145) + (0.05296^e234) + (0.0472^e235) - (0.00653^e245) + (0.22212^e345),rgb(0, 255, 0));
DrawCircle((0.04859^e123) + (0.25044^e124) + (0.23483^e125) - (0.08566^e134) - (0.25731^e135) - (0.91213^e145) + (0.07425^e234) + (0.04171^e235) - (0.14383^e245) + (0.31963^e345),rgb(0, 255, 0));
DrawCircle((0.00187^e123) + (0.01138^e124) - (0.00421^e125) + (0.0099^e134) - (0.16272^e135) - (0.96955^e145) + (0.00279^e234) - (0.0242^e235) - (0.14123^e245) + (0.11485^e345),rgb(0, 255, 0));
DrawCircle((0.03757^e123) + (0.16869^e124) + (0.14769^e125) - (0.27349^e134) - (0.44679^e135) - (0.93092^e145) - (0.00542^e234) - (0.02247^e235) - (0.07956^e245) + (0.09906^e345),rgb(0, 255, 0));
DrawCircle((0.03331^e123) + (0.16916^e124) + (0.1511^e125) - (0.12737^e134) - (0.30016^e135) - (0.94644^e145) + (0.01187^e234) - (0.01232^e235) - (0.11636^e245) + (0.15402^e345),rgb(0, 255, 0));
DrawCircle(-(0.02346^e123) - (0.14499^e124) - (0.15743^e125) - (0.3506^e134) - (0.52405^e135) - (0.88598^e145) - (0.07533^e234) - (0.09829^e235) - (0.10191^e245) + (0.21391^e345),rgb(0, 255, 0));
DrawCircle(-(0.00163^e123) - (0.00369^e124) + (0.00601^e125) - (0.08111^e134) + (0.08816^e135) - (0.0996^e145) + (0.05313^e234) - (0.0777^e235) + (0.0201^e245) - (0.9926^e345),rgb(255, 0, 0));
DrawCircle(-(0.00428^e123) - (0.00207^e124) + (0.00797^e125) - (0.08667^e134) + (0.06185^e135) - (0.13139^e145) + (0.06136^e234) - (0.0926^e235) + (0.06942^e245) - (0.98831^e345),rgb(255, 0, 0));
DrawCircle((0.02524^e123) - (0.01737^e124) - (0.00921^e125) + (0.07259^e134) + (0.23907^e135) - (0.138^e145) + (0.08771^e234) - (0.04676^e235) + (0.06415^e245) - (0.96505^e345),rgb(255, 0, 0));
DrawCircle((0.01871^e123) - (0.02141^e124) - (0.01754^e125) - (0.16457^e134) + (0.00628^e135) - (0.16144^e145) + (0.2358^e234) + (0.10674^e235) + (0.09888^e245) - (1.0178^e345),rgb(255, 0, 0));
DrawCircle((0.03803^e123) - (0.02297^e124) - (0.01411^e125) + (0.07392^e134) + (0.24209^e135) - (0.11877^e145) + (0.16327^e234) + (0.03098^e235) + (0.04187^e245) - (0.97901^e345),rgb(255, 0, 0));
DrawCircle((0.0571^e123) - (0.0239^e124) - (0.02377^e125) + (0.01516^e134) + (0.17752^e135) - (0.068^e145) + (0.34583^e234) + (0.20618^e235) + (0.05767^e245) - (1.02035^e345),rgb(255, 0, 0));
DrawCircle(-(0.0138^e123) + (0.01143^e124) - (0.0013^e125) - (0.10569^e134) + (0.05303^e135) - (0.03392^e145) + (0.00936^e234) - (0.13384^e235) + (0.10991^e245) - (0.98875^e345),rgb(255, 0, 0));
DrawCircle((0.01793^e123) - (0.01735^e124) - (0.01096^e125) + (0.16564^e134) + (0.33663^e135) - (0.22451^e145) - (0.02969^e234) - (0.15847^e235) + (0.13519^e245) - (0.90648^e345),rgb(255, 0, 0));
DrawCircle(-(0.01983^e123) + (0.00804^e124) + (0.02442^e125) - (0.0081^e134) + (0.16215^e135) - (0.07573^e145) - (0.10574^e234) - (0.23461^e235) - (0.03506^e245) - (0.96063^e345),rgb(255, 0, 0));
DrawCircle((0.00653^e123) - (0.00079^e124) - (0.02854^e125) + (0.008^e134) + (0.17581^e135) + (0.01366^e145) + (0.03149^e234) - (0.09863^e235) + (0.14948^e245) - (0.96805^e345),rgb(255, 0, 0));
DrawCircle((0.02326^e123) + (0.11549^e124) + (0.10063^e125) - (0.11884^e134) - (0.29296^e135) - (0.94054^e145) + (0.0147^e234) - (0.00346^e235) - (0.08076^e245) + (0.20278^e345),rgb(0,0,0));
DrawCircle(-(0.07506^e123) + (0.12322^e124) + (0.27679^e125) + (0.06632^e134) + (0.0079^e135) + (0.23158^e145) + (0.41334^e234) + (0.36559^e235) + (0.92412^e245) - (0.27949^e345),rgb(0,0,0));
DrawCircle((0.01293^e123) - (0.00826^e124) - (0.00599^e125) - (0.01109^e134) + (0.15494^e135) - (0.10406^e145) + (0.08558^e234) - (0.04998^e235) + (0.07156^e245) - (0.98244^e345),rgb(0,0,0));